package com.example.demo.utils;

import cn.hutool.core.io.IoUtil;
import com.example.demo.study.jdbc.DBUtils;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.apache.commons.collections.CollectionUtils;

import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.lang.reflect.Field;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;
import java.nio.charset.CharsetEncoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.LinkOption;
import java.nio.file.Path;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class CsvExportUtil {
    /**
     * CSV文件列分隔符
     */
    private static final String CSV_COLUMN_SEPARATOR = ",";

    /**
     * CSV文件行分隔符
     */
    private static final String CSV_ROW_SEPARATOR = "\r\n";
    public static final String CSV_VALUE_SEPARATOR = "\"";
    public static final String DATE_TIME_FORMAT = "yyyyMMddHHmmss";

    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    static class DataDTO {
        private String str1;
        private String str2;
    }

    public static void main(String[] args) throws Exception {
//        List<DataDTO> list = new ArrayList<>();
//        list.add(new DataDTO("a1", "b1"));
//        list.add(new DataDTO("a2", "b2"));
//        list.add(new DataDTO("a3", "b3"));
//        list.add(new DataDTO("a4", "b4"));
//        list.add(new DataDTO("a5", "b5"));
//        list.add(new DataDTO("a6", "b6"));
//
//        String[] titles = {"column1", "column2"};
//        DateTimeFormatter dtf = DateTimeFormatter.ofPattern(DATE_TIME_FORMAT);
//        StringBuilder sb = new StringBuilder();
//        String fileName = sb.append("csvExport_").append(dtf.format(LocalDateTime.now())).append(".csv").toString();
//        int count = (list.size() - 1) / 2;
//        List<List<DataDTO>> collect = Stream.iterate(0, n -> n + 1).limit(count)
//                .map(i -> list.stream().skip(i).limit(2).collect(Collectors.toList())).collect(Collectors.toList());
//        int size = 0;
//        boolean flag = false;
//        for (int i = 0; i < collect.size(); i++) {
//            size += collect.get(i).size();
//            flag = i == collect.size() - 1;
//            doExport(list, titles, new File(fileName), size, flag);
//        }
//        main1(args);


//        CharBuffer allocate = CharBuffer.allocate();

//        ByteBuffer byteBuffer = encoder.encode(buffer);
//
//        byte[] array;
//        int arrayLen = byteBuffer.limit();
//        if (arrayLen == byteBuffer.capacity()) {
//            array = byteBuffer.array();
//        } else {
//            // This will place two copies of the byte sequence in memory,
//            // until byteBuffer gets garbage-collected (which should happen
//            // pretty quickly once the reference to it is null'd).
//
//            array = new byte[arrayLen];
//            byteBuffer.get(array);
//        }
//
//        byteBuffer = null;

//        testexport();
        File file = new File("test.txt");
        System.out.println(file.getAbsoluteFile());
        System.out.println(file.getPath());
        System.out.println(file.getCanonicalPath());
        Path path = file.toPath();
        System.out.println(path.toString());
        System.out.println(path.getFileSystem());
        System.out.println(path.getRoot());
        System.out.println(path.toAbsolutePath());
        System.out.println(path.toRealPath(LinkOption.NOFOLLOW_LINKS));
        System.out.println(path.toUri());
        System.out.println(System.getProperty("user.dir"));
        System.out.println(System.getProperty("user.home"));

    }

    public static void testexport() throws Exception {
        long start1 = System.currentTimeMillis();
        Connection conn = DBUtils.getConnection();
        PreparedStatement ps = conn.prepareStatement("select * from ROOT.csv_tb");
        ResultSet resultSet = ps.executeQuery();
        System.out.println("get conn=" + (System.currentTimeMillis() - start1));

        List<List<String>> batchList = new ArrayList<>();
        int size = 60000;
        int i = 0;
        long start2 = System.currentTimeMillis();
        try (BufferedWriter bw = new BufferedWriter(new FileWriter("csvExport.csv" + System.currentTimeMillis(), true))) {
            while (resultSet.next()) {
                batchList.add(convertRs(resultSet));
                if (++i % size == 0) {
                    doExport3(bw, batchList);
                    batchList.clear();
                }
            }
            if (i % size != 0) {
                doExport3(bw, batchList);
                batchList.clear();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("export=" + (System.currentTimeMillis() - start2));


    }

    public static DateFormat df = new SimpleDateFormat("yyyyMMdd hh:mm:ss");

    private static List<String> convertRs(ResultSet resultSet) throws SQLException {
        List<String> list = new ArrayList<>();
        for (String s : header) {
            Object obj = resultSet.getObject(map.get(s));
            if (obj == null) {
                list.add(null);
            } else if (obj instanceof String) {
                list.add(resultSet.getString(map.get(s)));
            } else if (obj instanceof Date) {
                list.add(df.format((Date) obj));
            }
        }
        return list;
    }

    public static void main1(String[] args) throws CloneNotSupportedException, InterruptedException, IOException {
        int totals = 100000000;
        int segment = 20;
        // 写入5亿条数据
        // 开启20个线程
        ExecutorService service = Executors.newFixedThreadPool(segment);
        AtomicInteger incr = new AtomicInteger(0);
        CountDownLatch downLatch = new CountDownLatch(segment);
        long s = System.currentTimeMillis();
        for (int j = 0; j < segment; j++) {
            service.execute(() -> {
                RandomAccessFile acf;
                FileChannel fc = null;
                try {
                    String fName = "D:\\test\\tmp_" + incr.getAndIncrement() + ".txt";
                    acf = new RandomAccessFile(fName, "rw");
                    fc = acf.getChannel();
                    int offset = 0;
                    for (int i = 0; i < totals / segment / 10000; i++) { //25000000
                        //每次写1w个 数字
                        StringBuilder sb = new StringBuilder();
                        for (int k = 0; k < 10000; k++) {
                            sb.append(new Random().nextInt(10000000) + "\n");
                        }
                        byte[] bs = sb.toString().getBytes();
                        MappedByteBuffer mbuf = fc.map(FileChannel.MapMode.READ_WRITE, offset, bs.length);
                        mbuf.put(bs);
                        offset = offset + bs.length;
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    downLatch.countDown();
                    try {
                        fc.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            });
        }
        downLatch.await();
        System.out.println("await 唤醒， 小文件写入完毕! 耗時：" + (System.currentTimeMillis() - s));
        List<File> files = new ArrayList<File>();
        for (int i = 0; i < segment; i++) {
            files.add(new File("D:\\test\\tmp_" + i + ".txt"));
        }
        s = System.currentTimeMillis();
        //合併文件
        merge(files, "D:\\test\\last.txt");
        System.out.println("合併文件完毕! 耗時：" + (System.currentTimeMillis() - s));
        service.shutdown();
    }


    public static void merge(List<File> files, String to) {
        File t = new File(to);
        FileInputStream in = null;
        FileChannel inChannel = null;

        FileOutputStream out = null;
        FileChannel outChannel = null;
        try {
            out = new FileOutputStream(t, true);
            outChannel = out.getChannel();
            // 记录新文件最后一个数据的位置
            long start = 0;
            for (File file : files) {
                in = new FileInputStream(file);
                inChannel = in.getChannel();
                // 从inChannel中读取file.length()长度的数据，写入outChannel的start处
                outChannel.transferFrom(inChannel, start, file.length());
                start += file.length();
                in.close();
                inChannel.close();
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                out.close();
                outChannel.close();
            } catch (Exception e2) {
            }
        }
    }

    private static final String[] header = {"datecol",
            "numcol",
            "charcol",
            "nvarcol1", "nvarcol2", "nvarcol3", "nvarcol4", "nvarcol5", "nvarcol6", "nvarcol7", "nvarcol8", "nvarcol9", "nvarcol10", "nvarcol11", "nvarcol12", "nvarcol13", "nvarcol14", "nvarcol15", "nvarcol16", "nvarcol17", "nvarcol18", "nvarcol19", "nvarcol20"};
    private static Map<String, String> map = new HashMap<>();

    static {
        Arrays.stream(header).forEach(str -> map.put(str, str));
    }

    static String valueS = "\"";
    static String valuesS = ",";
    static String rowS = "\r\n";

    public static <T> void doExport3(BufferedWriter fw, List<List<String>> list) throws Exception {
        int max = 10000;
        int batch = list.size() > max ? list.size() / max : 0;
        for (int i = 0; i < batch; i++) {
            StringBuffer sb = new StringBuffer();
            for (int j = i * max; j < (i + 1) * max; j++) {
                for (String s : header) {
                    sb.append(valueS).append(s).append(valueS).append(valuesS);
                }
                sb.delete(sb.lastIndexOf(valuesS), sb.length() - 1);
                sb.append(rowS);
            }
            fw.write(sb.toString().toCharArray());
            fw.flush();
            sb.delete(0, sb.length() - 1);
        }
        if (batch == 0) {
            StringBuffer sb = new StringBuffer();
            for (int j = 0; j < list.size(); j++) {
                for (String s : header) {
                    sb.append(valueS).append(s).append(valueS).append(valuesS);
                }
                sb.delete(sb.lastIndexOf(valuesS), sb.length() - 1);
                sb.append(rowS);
            }
            fw.write(sb.toString().toCharArray());
            fw.flush();
            sb.delete(0, sb.length());
        }
    }

    public static char[] getCharArray(StringBuffer sb) throws CharacterCodingException {
        Charset charset = StandardCharsets.UTF_8;
        CharsetEncoder encoder = charset.newEncoder();
// No allocation performed, just wraps the StringBuilder.
        CharBuffer buffer = CharBuffer.wrap(sb);
//        ByteBuffer bytes = encoder.encode(buffer);
        return buffer.array();
    }


    /**
     * @param dataList 集合数据
     * @param titles   表头部数据
     */
    public static <T> void doExport(List<T> dataList, String[] titles, File file, int size, boolean flag) throws Exception {
        StringBuilder bud = new StringBuilder();
        BufferedOutputStream os;
        if (file.exists()) {
            os = new BufferedOutputStream(new FileOutputStream(file, true));
        } else {
            os = new BufferedOutputStream(new FileOutputStream(file));
            // 组装表头
            bud.append(CSV_VALUE_SEPARATOR);
            bud.append(String.join(CSV_VALUE_SEPARATOR + CSV_COLUMN_SEPARATOR + CSV_VALUE_SEPARATOR, titles));
            bud.append(CSV_VALUE_SEPARATOR);
            bud.append(CSV_ROW_SEPARATOR);
        }
        // 组装数据
        if (CollectionUtils.isNotEmpty(dataList)) {
            for (Object obj : dataList) {
                Field[] fields = obj.getClass().getDeclaredFields();
                for (Field field : fields) {
                    field.setAccessible(true);
                    Object o = field.get(obj);
                    if (o != null) {
                        bud.append(CSV_VALUE_SEPARATOR + field.get(obj).toString() + CSV_VALUE_SEPARATOR);
                    }
                    bud.append(CSV_COLUMN_SEPARATOR);
                }
                bud.deleteCharAt(bud.length() - 1);
                bud.append(CSV_ROW_SEPARATOR);
            }
        }

        String value = bud.toString();
        // 如果是最后一批，删除最有一个回车
        if (flag) {
            value = value.substring(0, bud.lastIndexOf(CSV_ROW_SEPARATOR));
        }

        // 写出响应
        os.write(value.getBytes(StandardCharsets.UTF_8)); //windows下防止中文乱码
        os.flush();
        // 如果是最后一批，加上date、size，
        // todo
        if (flag) {

        }
        os.close();
    }

    /**
     * 设置Header
     *
     * @param fileName
     * @param response
     * @throws UnsupportedEncodingException
     */
    public static void responseSetProperties(String fileName, HttpServletResponse response) throws UnsupportedEncodingException {
        // 设置文件后缀
        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
        String fn = fileName + sdf.format(new Date());
        response.setContentType("application/octet-stream");
        response.setContentType("Content-type:application/vnd.ms-excel;charset=UTF-8");
        response.setHeader("Content-Disposition", "attachment;filename=\"" + fn + ".csv\"");
    }

}
